
from WtfEngine import *;
from Util import *;
import Commodities;
from math import sqrt;
from Control import *;

class Trader:
	def __init__(self):
		self.commodities = {};
		self.demand = {};
	
	def AddCommodity(self, c, price = 1.0, maxPrice = 1.0, demand = 0):
		n = c.GetName();
		self.commodities[n] = tc = DictToObject({
			'commodity': c,
			'price': price,
			'maxPrice': maxPrice,
			'demand': demand,
			'sales': 0
		});
		if not demand == 0:
			self.SetDemand(n, demand);
		return tc;
	
	def SetDemand(self, n, demand):
		assert n in self.commodities;
		self.demand[n] = float(demand);
		self.commodities[n].demand = demand;
	
	def Process(self):
		for (n, d) in self.demand.iteritems():
			if not d == 0:
				newAmt = self.commodities[n].commodity.amt - d;
				if newAmt < 0:
					self.commodities[n].commodity.amt = 0;
					# Post Shortage
				else:
					self.commodities[n].commodity.amt = newAmt;
	
	def SetPrice(self, data, n, tc):
		lastDemand = tc.demand;
		if n in self.demand:
			tc.demand = self.demand[n];
		else:
			tc.demand = 0.0;
		
		# Sale price = Average Price * (Desired Reserves / Reserves)
		# Prices are measured in Capital Value Heuristic (CVH) units, a common measure
		# of real value.
		if tc.demand > 0:
			tc.price = data[n].avgPrice * (
				((float(data[n].vol)/ max(0.0001, float(data[n].count))) + tc.demand)
				/ max(0.1 / tc.demand, float(tc.commodity.amt)) );
		elif lastDemand < 0:
			tc.price = tc.price * sqrt((tc.sales + 1.0) / (1.0 - lastDemand));
			tc.price = (tc.price + data[n].avgPrice) * 0.5;
		else:
			tc.price = data[n].avgPrice;
		
		if tc.demand >= 0:
			tc.demand += (tc.maxPrice / data[n].avgPrice) - 1.0;
		else:
			tc.demand -= (tc.commodity.amt + tc.demand) / -tc.demand;
		
		# Max Purchase price = Average Price * (Demand / Reserves)
		tc.maxPrice = tc.price * 0.75;
		
	def SetPrices(self, data):
		for (n,tc) in self.commodities.iteritems():
			self.SetPrice(data, n, tc);

	def StartTrading(self, data, market):
		for (n,tc) in self.commodities.iteritems():
			tc.sales = 0;
			
	def DoTrading(self, data, market):
		traded = False;
		for (n,tc) in self.commodities.iteritems():			
			if tc.demand > 0:
				# Start from lowest available price on the market
				prices = sorted(data[n].bestPrices.keys());
				for p in prices:
					for trader in data[n].bestPrices[p]:
						dtc = trader.commodities[n];
						if not trader == self:
						
							# Find a demand of the trader which we can satisfy
							for (on,otc) in trader.commodities.iteritems():
								if on in self.commodities:
									stc = self.commodities[on];
									
									DebugPrint("Trading " + on + " for " + n);
									if otc.demand > 0 and dtc.price <= tc.maxPrice and stc.price <= otc.maxPrice:
										# Compute tax rates for demand and supply
										dr = market.society.GetSalesTaxRate(self, n);
										sr = market.society.GetSalesTaxRate(trader, on);
										
										# Compute available amounts
										damax = min(dtc.commodity.amt / (1 + dr), tc.demand);
										samax = min(stc.commodity.amt / (1 + sr), otc.demand);
										
										# Compute amount to trade based upon
										# available amounts (normalized to CVH units)
										cvh = min(damax * dtc.price, samax * stc.price);
										
										if cvh > 0:
											# Perform the trade
											da = cvh / dtc.price;
											dtax = dr * da;
											sa = cvh / stc.price;
											stax = sr * sa;
											
											dtc.commodity.amt -= da + dtax;
											dtc.sales += da + dtax;
											tc.commodity.amt += da;
											tc.demand -= da;
											market.CollectSalesTax(self, n, da, dtax);
											
											stc.commodity.amt -= sa + stax;
											stc.sales += sa + stax;
											otc.commodity.amt += sa;
											otc.demand -= sa;
											market.CollectSalesTax(trader, on, sa, stax);
											
											DebugPrint("Traded " + str(sa) + " of " + on + " for " + str(da) + " of " + n);
											traded = True;
											
											# Update market data
											data[n].bestPrices[p].remove(trader);
											data[on].bestPrices[stc.price].remove(self);
											trader.SetPrice(data, n, dtc);
											self.SetPrice(data, on, stc);
											if dtc.price in data[n].bestPrices:
												data[n].bestPrices[dtc.price].append(trader);
											else:
												data[n].bestPrices[dtc.price] = [trader];
											if stc.price in data[on].bestPrices:
												data[on].bestPrices[stc.price].append(self);
											else:
												data[on].bestPrices[stc.price] = [self];
		
		return traded;
	
class Market:
	def __init__(self, society):
		self.traders = []
		self.society = society;
		self.lastTradeAmounts = {};
		self.marketHistory = {};
		
	def GetMarketData(self):
		return self.marketData;
	
	def CalculateMarketData(self):
		ps = {};
		for t in self.traders:
			for (n,tc) in t.commodities.iteritems():
				c = tc.commodity;
				p = tc.price;
				
				if n in ps:
					ps[n] = DictToObject({
						'count': ps[n].count + 1,
						'vol': ps[n].vol + c.amt,
						'mktCap': ps[n].mktCap + (c.amt * p),
						'avgPrice': ((ps[n].avgPrice * ps[n].vol) + (p * c.amt))
							/ max(1.0, ps[n].vol + c.amt),
						'bestPrices': ps[n].bestPrices,
					});
					if p in ps[n].bestPrices:
						ps[n].bestPrices[p].append(t);
					else:
						ps[n].bestPrices[p] = [t];
				else:
					ps[n] = DictToObject({
						'count': 1,
						'vol': c.amt,
						'mktCap': c.amt * p,
						'avgPrice': p,
						'bestPrices': {p: [t]}
					});
		for (n,cdata) in ps.iteritems():
			if cdata.avgPrice == 0:
				cdata.avgPrice = 1.0;
		
		self.marketHistory[Controller.GetInst().game.turn] = ps;
		self.marketData = ps;
		return ps;
	
	def GetTradeAmounts(self):
		return self.lastTradeAmounts;
	
	def CollectSalesTax(self, trader, n, amt, tax):
		if n in self.lastTradeAmounts:
			self.lastTradeAmounts[n] += amt;
		else:
			self.lastTradeAmounts[n] = amt;
			
	def SetPrices(self):
		map(lambda t: t.SetPrices(self.marketData), self.traders);
		self.CalculateMarketData();
	
	def DoTrading(self):
		self.lastTradeAmounts = {};
		
		for trader in self.traders:
			trader.StartTrading(self.marketData, self);
		
		DebugPrint("Starting Trading");
		trading = True;
		while trading:
			trading = False;
			for trader in self.traders:
				trading = trading or trader.DoTrading(self.marketData, self);
	
	def Process(self):
		map(lambda t: t.Process(), self.traders);
	
	